////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Εμπλουτίζουμε το sigleton "Skiniko" με properties και μεθόδους που έχουν νόημα
// μόνο στον server.

// Η μέθοδος "sinedriaEnimerosi" διατρέχει όλες τις συνεδρίες και
// ενημερώνει τυχόν ανοικτά "feredata" αιτήματα των συνεδριών με
// τυχόν νέες κινήσεις που αοφορούν τους αντίστοιχους clients.

Skiniko.sinedriaEnimerosi = function() {
	Skiniko.sinedriaWalk(function(login, sinedria) {
		sinedria.enimerosi();
	});
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Εμπλουτίζουμε την κλάση "Trapezi" με κάποιες επιπλέον μεθόδους
// που αφορούν στον server.

// Η μέθοδος "rowSet" δέχεται μια εγγραφή τραπεζιού από την database και θέτει
// τα αντίστοιχα properties. Θέτει επίσης τις βάσεις για άλλα στοιχεία του
// τραπεζιού, π.χ. παράμετροι, θεατές, προσκλήσεις κλπ.
// Η μέθοδος πρέπει να εφαρμόζεται σε νεόκοπο αντικείμενο τραπεζιού.
// Η μέθοδος επιστρέφει το ίδιο το τραπέζι και χρησιμοποποιείται για αρχικοποίηση
// του τραπεζιού με βάση τα στοιχεία της database.

Trapezi.prototype.rowSet = function(row) {
	this.kodikos = row['kodikos'];
	this.stisimo = row['stisimo'];

	// Πιθανόν να χρειαστούν διορθώσεις στα στοιχεία του τραπεζιού. Αυτές θα
	// δημιουργήσουν update SQL query ("diorthosi" το οποίο θα επιχειρήσουμε
	// να τρέξουμε λίγο πριν το τέλος της διαδικασίας που ακολουθεί.

	var diorthosi = '';

	for (var i = 1; i <= Prefadoros.thesiMax; i++) {
		if (!row['pektis' + i]) {
			delete this['pektis' + i];
			delete this['apodoxi' + i];
			continue;
		}

		// Ακολουθεί έλεγχος διπλοκαθίσματος παίκτη. Αν ο ανά χείρας παίκτης
		// βρεθεί και σε προηγούμενη θέση του τραπεζιού, τότε εκκενώνουμε την
		// προηγούμενη θέση.

		for (var j = 1; j < i; j++) {
			if (this['pektis' + j] != row['pektis' + i]) continue;
			console.error('Trapezi.rowSet: ίδιος παίκτης στις θέσεις ' + j + ' και ' + i + ' (διορθώθηκε)');
			delete this['pektis' + j];
			delete this['apodoxi' + j];
			diorthosi += ", `pektis" + j + "` = NULL, `apodoxi" + j + "` = 'ΟΧΙ'";
			break;
		}

		this['pektis' + i] = row['pektis' + i];
		this['apodoxi' + i] = row['apodoxi' + i].isNai();
	}

	// Όταν διαβάζουμε τα τραπέζια από την database θέτουμε το "poll" στο τρέχον
	// timestamp καθώς έχει προηγηθεί διακοπή και το παλαιό "poll" είναι λανθασμένο.

	this.poll = globals.toraServer();

	if (diorthosi) {
		var conn = DB.connection();
		conn.query('UPDATE `trapezi` SET `poll` = `poll`' + diorthosi +
			' WHERE `kodikos` = ' + this.kodikos, function(conn) {
			conn.free();
		});
	}

	return this;
}

// Η μέθοδος "trparamRowSet" δέχεται μια παράμετρο τραπεζιού από την database και την
// εντάσσει στη λίστα παραμέτρων του τραπεζιού.

Trapezi.prototype.trparamRowSet = function(row) {
	this.trparamSet(row['param'], row['timi']);
	return this;
}

// Η μέθοδος "feredataFormat" επιστρέφει τα δεδομένα του τραπεζιού σε μορφή κατάλληλη
// για αποστολή σε αίτημα παραλαβής δεδομένων. Το τραπέζι επιστρέφεται σε JSON format
// με τα πεδία του τραπεζιού και των παραμέτρων σε συντομευμένη μορφή. Άλλα στοιχεία
// των τραπεζιών, π.χ. προσκλήσεις, αποστέλλονται σε ξεχωριστά κεφάλαια καθώς εμπλέκουν
// στοιχεία συνεδριών και έτσι δεν θα μπορούσε να ελεγχθεί από τους clients η ακεραιότητα
// των στοιχείων καθώς οι συνεδρίες αποστέλλονται αργότερα.

Trapezi.prototype.feredataFormat = function(sinedria) {
	var trapezi = {}, thesi;

	trapezi[Prefadoros.trapeziEco('kodikos')] = parseInt(this.kodikos);
	trapezi[Prefadoros.trapeziEco('stisimo')] = parseInt(this.stisimo);
	for (thesi in Prefadoros.thesiValid) {
		if (this.pektis(thesi)) trapezi[Prefadoros.trapeziEco('pektis' + thesi)] = this.pektis(thesi);
		if (this.oxiApodoxi(thesi)) trapezi[Prefadoros.trapeziEco('apodoxi' + thesi)] = 0;
	}

	this.
	trparamFeredataFormat(trapezi).
	dianomiFeredataFormat(trapezi);

	return globals.stringify(trapezi);
}

Trapezi.prototype.trparamFeredataFormat = function(trapezi) {
	var param, trparam = {}, trparamYes = false;

	for (param in this.trparam) {
		trparam[Prefadoros.trparamEco(param)] = this.trparam[param];
		trparamYes = true;
	}
	if (trparamYes) trapezi[Prefadoros.trapeziEco('trparam')] = trparam;

	return this;
}

Trapezi.prototype.dianomiFeredataFormat = function(trapezi) {
	var i, dianomi = [];

	for (i = 0; i < this.dianomi.length; i++) {
		dianomi[i] = this.dianomi[i].feredataFormat();
	}
	if (i) trapezi[Prefadoros.trapeziEco('dianomi')] = dianomi;

	return this;
}

Dianomi.prototype.rowSet = function(row) {
	var i;

	for (i in row) this[i] = row[i];
	return this;
}

Dianomi.prototype.feredataFormat = function() {
	var i, x = {};

	x[Prefadoros.dianomiEco('kodikos')] = this.kodikos;
	x[Prefadoros.dianomiEco('enarxi')] = this.enarxi;
	x[Prefadoros.dianomiEco('dealer')] = this.dealer;
	for (i in Prefadoros.thesiValid) {
		x[Prefadoros.dianomiEco('kasa' + i)] = this['kasa' + i];
		x[Prefadoros.dianomiEco('metrita' + i)] = this['metrita' + i];
	}

	return x;
}

Energia.prototype.feredataFormat = function(sinedria) {
	var x = {};

	x[Prefadoros.energiaEco('pektis')] = this['pektis'];
	x[Prefadoros.energiaEco('idos')] = Prefadoros.energiaIdosEco(this['idos']);
	x[Prefadoros.energiaEco('pote')] = this['pote'];

	switch (this['idos']) {
	case 'ΔΙΑΝΟΜΗ':
		x[Prefadoros.energiaEco('data')] = sinedria.isPektis() ?
			sinedria.kripseFila(this['data']) : this['data'];
		break;
	default:
		x[Prefadoros.energiaEco('data')] = this['data'];
		break;
	}

	return globals.stringify(x);
}

Trapezi.prototype.simetoxiGet = function(login) {
}

Trapezi.prototype.thesiEpilogi = function(login, keni) {
	var thesi = false, sinedria;

	// Εν υπάρχει προηγούμενη συμμετοχή του παίκτη στο τραπέζι,
	// προτιμούμε την προηγούμενη θέση.

	if (this.hasOwnProperty('simetoxi') && this.simetoxi.hasOwnProperty(login)) thesi = this.simetoxi[login];

	// Αν δεν επελέγη θέση με τη διαδικασία που προηγήθηκε, τότε
	// επιλέγω τη θέση που κατέχει ο παίκτης τώρα (μπορεί να είναι
	// θεατής).

	if (!thesi) {
		sinedria = Skiniko.isSinedria(login);
		if (sinedria) thesi = sinedria.isThesi();
	}

	// Αν δεν ζητώ απαραίτητα κενή θέση, τότε επιστρέφω τη θέση που επελέγη
	// με τις διαδικασίες που προηγήθηκαν. Αν δεν έχει επιλεγεί τέτοια θέση
	// επιστρέφω την πρώτη θέση.

	if (!thesi) thesi = 1;
	if (!keni) return thesi;

	// Ζητείται κενή θέση. Αν η θέση που επελέγη με τις παραπάνω διαδικασίες
	// είναι κενή την επιστρέφω, αλλιώς επιστρέφω την πρώτη κενή θέση.

	if (this.isKeni(thesi)) return thesi;
	return this.keniThesi();
}

Trapezi.prototype.klidoma = function(conn, onsuccess, onerror) {
	conn.klidoma('TR' + this.kodikos, onsuccess, onerror);
}

Trapezi.prototype.xeklidoma = function(conn, callback) {
	conn.xeklidoma('TR' + this.kodikos, callback);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Για να γίνει νέα διανομή σε τραπέζι πρέπει να κλειδωθεί το τραπέζι και στα
// πλαίσια ενιαίας transaction να δημιουργηθεί record διανομής και record τής
// πρώτης ενέργειας που είναι η διανομή (μοιρασιά). Η διαδικασία θα γίνει
// αλυσιδωτά. Για να αποφύγουμε την περίπτωση της διπλοδιανομής από τον ίδιο
// παίκτη δεχόμαστε ως παράμετρο τη θέση τού παίκτη που αιτείται τη νέα διανομή.
// Αν αυτός δεν είναι ο παίκτης που έχει σειρά να μοιράσει, τότε ακυρώνουμε
// σιωπηλά τη διαδικασία ως μη γενόμενη.

// Εκκινούμε τη διαδικασία με τη δημιουργία μιας transaction.

Trapezi.prototype.neaDianomi = function(thesi) {
	var
	trapezi = this,
	conn = DB.connection();

	conn.transaction(function(conn) {
		trapezi.neaDianomi2(conn, thesi);
	});

	return this;
}

// Επιχειρούμε να κλειδώσουμε το τραπέζι και εφόσον τα καταφέρουμε προχωρούμε
// στην επόμενη φάση που είναι η δημιουργία του record διανομής.

Trapezi.prototype.neaDianomi2 = function(conn, thesi) {
	var trapezi = this;

	this.klidoma(conn, function(conn) {
		trapezi.neaDianomi3(conn, thesi);
	}, function(conn) {
		conn.rollback();
	});

	return this;
}

// Σε περίπτωση που το τραπέζι κλειδώθηκε επιτυχώς δημιουργούμε νέα
// διανομή.

Trapezi.prototype.neaDianomi3 = function(conn, thesi) {
	var
	trapezi = this,
	dealer,
	query;

	// Υπολογίζουμε τη θέση τού παίκτη που έχει σειρά να μοιράσει και
	// ελέγχουμε αν είναι αυτός που αιτήθηκε τη νέα διανομή.

	dealer = (this.dianomi.length % 3) + 1;
	if (dealer != thesi) {
		this.xeklidoma(conn, function(conn) {
			conn.rollback();
		});
		console.error('επιχειρήθηκε επαναδιανομή από τον ίδιο παίκτη');
		return this;
	}

	query = 'INSERT INTO `dianomi` (`trapezi`, `dealer`) VALUES (' +
		this.kodikos + ', ' + dealer + ')';
	conn.query(query, function(conn) {
		trapezi.neaDianomi4(conn, new Dianomi({
			'kodikos': conn.insertId,
			'trapezi': trapezi.kodikos,
			'dealer': dealer,
		}));
	});

	return this;
}

Trapezi.prototype.neaDianomi4 = function(conn, dianomi) {
	var
	trapezi = this,
	idos = 'ΔΙΑΝΟΜΗ',
	data,
	query;

	data = Trapoula.dianomi();
	query = 'INSERT INTO `energia` (`dianomi`, `pektis`, `idos`, `data`) VALUES (' +
		dianomi.kodikos + ', ' + dianomi.dealer + ', ' +
		conn.escape(idos) + ', ' + conn.escape(data) + ')';
	conn.query(query, function(conn) {
		trapezi.neaDianomi5(conn, dianomi, new Energia({
			'trapezi': trapezi.kodikos,
			'idos': idos,
			'pektis': dianomi.dealer,
			'data': data,
		}));
	});

	return this;
}

Trapezi.prototype.neaDianomi5 = function(conn, dianomi, energia) {
	var trapezi = this;

	this.xeklidoma(conn, function(conn) {
		conn.commit();
	});

	this.dianomiAdd(dianomi);

	// Εισάγεται κίνηση διανομής στο transaction log, χωρίς
	// αυτόματη ενημέρωση των clients.

	new Kinisi({
		'?': 'DN',
		't': this.kodikos,
		'd': dianomi.kodikos,
		'l': dianomi.dealer,
	}).kinisiAdd(false);
	console.log(this.kodikos + ': νέα διανομή (' + dianomi.kodikos + ')');

	this.energiaAdd(energia);

	// Τα φύλλα τού τζόγου εισάγονται στο τραπέζι μόνο στον server και κατόπιν
	// θα κοινοποιηθούν όπου και όποτε χρειαστεί.

	this.tzogos = energia.data.substr(60, 4).toXartosia();

	// Εισάγεται τώρα κίνηση ενέργειας διανομής με τα φύλλα των παικτών.
	// Εδώ έχουμε και αυτόματη ενημέρωση των clients.

	new Kinisi({
		'?': 'EG',
		't': this.kodikos,
		'i': Prefadoros.energiaIdosEco('ΔΙΑΝΟΜΗ'),
		'p': dianomi.dealer,
		'd': energia.data.substr(0, 60),	// δεν στέλνουμε τον τζόγο
	}).kinisiAdd();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Εμπλουτίζουμε την κλάση "Sinedria" με κάποιες επιπλέον μεθόδους που αφορούν
// στον server.

// Η μέθοδος "rowSet" δέχεται μια εγγραφή συνεδρίας από την database και θέτει
// τα αντίστοιχα properties. Θέτει επίσης τις βάσεις για επιπλέον στοιχεία της
// συνεδρίας, π.χ. παράμετροι παίκτη κλπ.
// Η μέθοδος επιστρέφει την ίδια τη συνεδρία και χρησιμοποποιείται για αρχικοποίηση
// της συνεδρίας με βάση τα στοιχεία της database.

Sinedria.prototype.rowSet = function(row) {
	this.pektis = row['pektis'];
	this.klidi = row['klidi'];
	this.ip = row['ip'].validIp();

	// Όταν διαβάζουμε τις συνεδρίες από την database θέτουμε το "poll" στο τρέχον
	// timestamp καθώς έχει προηγηθεί διακοπή και το παλαιό "poll" είναι λανθασμένο.
	// Παράλληλα κάνουμε reset και το "isodos" στο τρέχον timestamp ώστε να
	// μην γίνονται λανθασμένες χρεώσεις.

	this.poll = globals.toraServer();
	this.isodos = this.poll;

	// Το property "dbpoll" δείχνει πότε ενημερώθηκε το "poll" της συνεδρίας στην
	// database. Αρχικά τίθεται μηδέν ώστε να έχουμε άμεση ενημέρωση στην database.

	this.dbpoll = 0;

	// Ελέγχουμε, διορθώνουμε και επαναθέτουμε τα στοιχεία θέσης της συνεδρίας.
	// Αν δεν υπάρχει τραπέζι κάνουμε απόπειρα επανεντοπισμού.

	var trapezi = Skiniko.isTrapezi(row['trapezi']);
	if (!trapezi) return this.entopismos();

	// Αν δεν υπάρχει θέση  κάνουμε απόπειρα επανεντοπισμού.

	var thesi = Prefadoros.isThesi(row['thesi']);
	if (!thesi) return this.entopismos();

	// Διαπιστώσαμε ότι η συνεδρία έχει συμπληρωμένα στοιχεία θέσης και προχωρούμε
	// σε περαιτέρω ελέγχους. Ελέγχουμε πρώτα αν ο σύνεδρος εμφανίζεται ως παίκτης
	// του τραπεζιού και αν ναι αγνοούμε τη θέαση.

	var iseht = trapezi.thesiPekti(this.pektis);
	if (iseht) return this.trapeziSet(trapezi.kodikos, iseht);

	if (row['simetoxi'] == 'ΠΑΙΚΤΗΣ') return this.trapeziSet(trapezi.kodikos, thesi);
	return this.trapeziSet(trapezi.kodikos, thesi, true);
}

// Η μέθοδος "entaxi" εντάσσει μια συνεδρία στο σκηνικό του server, θέτει
// τα στοιχεία θέσης και δημοσιοποιεί την ένταξη στους clients. Παράλληλα,
// ελέγχει την ύπαρξη τού σχετικού παίκτη στο σκηνικό τού server.

Sinedria.prototype.entaxi = function() {
	var pektis = Skiniko.fetchPektis(this.fetchProp('pektis'));
	pektis.used = globals.toraServer();

	// Ελέγχεται τυχόν υπάρχουσα συνεδρία του ιδίου παίκτη.
	// Αν υπάρχει συνεδρία και έχει αίτημα σε αναμονή κλείνουμε
	// το αίτημα.

	var sinedria = Skiniko.isSinedria(this.pektis);
	if (sinedria) sinedria.feredataClose();

	// Εντάσσεται η συνεδρία στο σκηνικό, συμπληρώνονται τα στοιχεία
	// θέσης και η ένταξη της συνεδρίας κοινοποιείται στους clients.
	// Στην κοινοποίηση περιλαμβάνονται και στοιχεία και παράμετροι
	// τού σχετικού παίκτη.

	sinedria = this.entopismos().sinedriaAdd();
	var kinisi = new Kinisi({
		'?': 'SN',
		'p': sinedria.pektis,
		'i': sinedria.ip,
		'o': pektis.onoma,
		'r': {},
	});
	for (var param in pektis.peparam) {
		kinisi.r[Prefadoros.peparamEco(param)] = pektis.peparam[param];
	}
	kinisi.kinisiAdd();
	return this;
}

Sinedria.prototype.apentaxi = function() {
	this.feredataClose().sinedriaDel();
	(new Kinisi({
		'?': 'NS',
		'p': this.pektis,
	})).kinisiAdd();
	return this;
}

// Η μέθοδος "sinedriaEpafi" ενημερώνει το poll timestamp της συνεδρίας στο σκηνικό
// του server. Η ενημέρωση στην database γίνεται σε αραιότερα διαστήματα για να
// μειώσουμε τον φόρτο του database server. Η μέθοδος επιστρέφει την συνεδρία.

Sinedria.prototype.sinedriaEpafi = function() {
	this.poll = globals.toraServer();
	if (this.poll - this.dbpoll < 300) return this;

	var conn = DB.connection();
	query = 'UPDATE `sinedria` SET `poll` = NOW() WHERE `pektis` = BINARY ' +
		conn.escape(this.pektis);
	conn.query(query, function(conn) { conn.free(); });

	// Αφήνουμε επίτηδες την εσωτερική ενημέρωση του "dbpoll" έξω από
	// το πραγματικό database transaction, δεν υπάρχει απολύτως κανένας
	// λόγος να μπλεχτούμε αλλιώς.

	this.dbpoll = this.poll;
	return this;
}

// Η μέθοδος "feredataFormat" επιστρέφει τα δεδομένα της συνεδρίας σε μορφή κατάλληλη
// για αποστολή σε αίτημα παραλαβής δεδομένων. Η συνεδρία επιστρέφεται σε JSON format
// με τα πεδία της συνεδρίας και των παραμέτρων του παίκτη σε συντομευμένη μορφή.

Sinedria.prototype.feredataFormat = function() {
	var sinedria = {};
	sinedria[Prefadoros.sinedriaEco('pektis')] = this.pektis;
	sinedria[Prefadoros.sinedriaEco('ip')] = this.ip;
	sinedria[Prefadoros.sinedriaEco('isodos')] = this.isodos;
	if (this.isTrapezi()) {
		sinedria[Prefadoros.sinedriaEco('trapezi')] = this.trapezi;
		sinedria[Prefadoros.sinedriaEco('thesi')] = this.thesi;
		if (this.isTheatis()) sinedria[Prefadoros.sinedriaEco('theatis')] = 1;
	}

	return globals.stringify(sinedria);
}

// Η μέθοδος "simetoxiSave" κρατάει τη θέση του παίκτη/θεατή στο τραπέζι σε
// εσωτερική λίστα του τραπεζιού δεικτοδοτημένη με το login name, ούτως ώστε
// αν χρειαστεί ο παίκτης/θεατής να επανεισέλθει στο τραπέζι να βρεθεί στην
// ίδια θέση, εφόσον βέβαια είναι αυτό δυνατόν. Η μέθοδος επιστρέφει τη συνεδρία.

Sinedria.prototype.simetoxiSave = function() {
	var trapezi = this.isTrapezi();
	if (!trapezi) return this;
	trapezi = Skiniko.isTrapezi(trapezi);
	if (!trapezi) return this;

	if (!trapezi.hasOwnProperty('simetoxi')) trapezi.simetoxi = {};
	trapezi.simetoxi[this.pektis] = this.thesi;
	return this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "isFeredata" επιστρέφει τυχόν ανοικτό αίτημα παραλαβής
// δεδομένων για τη συνεδρία. Αν δεν υπάρχει αίτημα για τη συνεδρία
// επιστρέφει undefined.

Sinedria.prototype.isFeredata = function() {
	return this.feredata;
}

// Η μέθοδος "oxiFeredata" επιστρέφει true εφόσον ΔΕΝ υπάρχει ανοικτό
// αίτημα παραλαβής δεδομένων για την ανά χείρας συνεδρία, αλλιώς
// επιστρέφει false.

Sinedria.prototype.oxiFeredata = function() {
	return !this.isFeredata();
}

// Η μέθοδος "feredataAnamoni" δέχεται ένα αίτημα παραλαβής δεδομένων και το
// αναθέτει στην property "feredata". Αν όλα λειτουργούν σωστά, δεν πρέπει να
// υπάρχει ήδη αίτημα σε αναμονή. Η μέθοδος επιστρέφει την συνεδρία.

Sinedria.prototype.feredataAnamoni = function(nodereq) {
	if (this.isFeredata()) globals.fatal('Sinedria.feredataAnamoni: υπάρχει ήδη αίτημα σε αναμονή');
	this.feredata = nodereq;
	return this;
}

// Η μέθοδος "enimerosi" ελέγχει την τελευταία κίνηση που έχει αποσταλεί
// και εφόσον εντοπίσει νέες κινήσεις που αφορούν την εν λόγω συνεδρία,
// αποστέλλει τις κινήσεις αυτές ως json strings και κλείνει το request.
// Οι κινήσεις δεν αποστέλλονται άκριτα σε όλους τους clients, αλλά
// μεσολαβεί διαδικασία ελέγχου που περιλαμβάνει έλεγχο σχετικότητας
// των κινήσεων με τους επιμέρους clients, και προσαρμογή τών δεδομένων
// προς αποφυγήν κοινοποίησης προσωπικών δεδομένων σε clients που δεν
// τους αφορούν. Αν δεν εντοπιστούν κινήσεις προς αποστολή, το request
// παραμένει εν αναμονή. Η μέθοδος επιστρέφει την συνεδρία.

Sinedria.prototype.enimerosi = function() {
	// Αν δεν υπάρχει αίτημα σε αναμονή αυτή τη στιγμή, τότε μάλλον
	// βρισκόμαστε σε φάση που έχει ήδη απαντηθεί προηγούμενο αίτημα
	// και οσονούπω θα μας έρθει νέο αίτημα από τον client. Εξάλλου,
	// το νέο αίτημα που θα υποβληθεί θα εντοπίσει αμέσως τυχόν νέες
	// κινήσεις που έχουν δημιουργηθεί στο ενδιάμεσο διάστημα και θα
	// τις επιστρέψει στον αιτούντα client.

	var nodereq = this.isFeredata();
	if (!nodereq) return this;

	// Θα διατρέξουμε τώρα το transaction log ξεκινώντας από το σημείο
	// τής προηγούμενης ενημέρωσης τής συγκεκριμένης συνεδρίας και τυχόν
	// νέες κινήσεις που αφορούν τον παίκτη τής συνεδρίας θα αποσταλλούν,
	// πιθανώς προσαρμοσμένες, ως array "k". Αν δεν εντοπιστούν κινήσεις
	// προς αποστολή το αίτημα θα παραμείνει ανοικτό, αλλιώς θα κλείσει.

	var sep = ',k:[';
	var lk = Skiniko.kinisi.length;
	for (var i = this.kinisi; i < lk; i++) {
		var idos = Skiniko.kinisi[i].idos();
		if (Skiniko.kinisi[i].isAdiafori(this.pektis, idos)) continue;
		nodereq.write(sep + Skiniko.kinisi[i].prosarmogi(this.pektis, idos));
		sep = ',';
	}

	// Ενημερώνουμε τον δείκτη τελευταίας αποσταλείσης κινήσεως για
	// τη συγκεκριμένη συνεδρία.

	this.kinisi = lk;

	// Αν δεν εντοπίστηκαν νέες κινήσεις που να αφορούν τη συνεδρία
	// δεν αποστέλλουμε καμία απάντηση και το αίτημα παραμένει σε
	// αναμονή.

	if (sep !== ',') return this;

	// Υπήρξαν κινήσεις που απεστάλησαν στον client της συνεδρίας,
	// επομένως κλείνουμε την απάντηση και εφόσον ο παραλήπτης είναι
	// «ζωντανός» θα μας υποβάλει νέο αίτημα αμέσως μετά την επεξεργασία
	// των δεδομένων που του αποστέλλουμε τώρα.

	this.feredataClose(']');
	return this;
}

// Η μέθοδος "feredataClose" κλείνει το αίτημα παραλαβής δεδομένων εφόσον,
// βεβαίως, υπάρχει ανοικτό τέτοιο αίτημα για την ανά χείρας συνεδρία. Αν
// έχει δοθεί παράμετρος αποστέλλεται ως data πριν κλείσει το αίτημα.
// Θα ήταν επικίνδυνο να κάνουμε το delete μετά το κλείσιμο της απάντησης,
// καθώς θα υπήρχε περίπτωση να δρομολογηθεί νέο αίτημα από τον client πριν
// το delete με πιθανή παρενέργεια τη διαγραφή της νέας εγγραφής. Αν δεν
// δοθούν δεδομένα τότε απαντάμε με κωδικό string κλεισίματος συνεδρίας.
// Η μέθοδος επιστρέφει την ίδια τη συνεδρία.

Sinedria.prototype.feredataClose = function(data) {
	var nodereq = this.isFeredata();
	if (!nodereq) return this;

	// Διαγράφουμε το αίτημα από τη συνεδρία, επομένως στο μεσοδιάστημα
	// που ακολουθεί να φαίνεται ότι η συνεδρία δεν έχει ανοικτό αίτημα.

	delete this.feredata;

	// Αν δεν έχουμε περάσει δεδομένα προς αποστολή, τότε σημαίνει ότι
	// απλώς επιθυμούμε να ενημερώσουμε τον αιτούντα ότι η συνεδρία έχει
	// κλείσει.

	nodereq.end(data ? data : ',"-":0');
	return this;
}

// Η μέθοδος "kripseFila" δέχεται ένα string χαρτωσιάς 30 φύλλων και επιστρέφει
// string χαρτωσιάς με κλειστά τα φύλλα των άλλων δύο παικτών.

Sinedria.prototype.kripseFila = function(fila) {
	return Skiniko.fetchPektis(this.pektis).kripseFila(fila, this.thesi);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@
//
// Εμπλουτίζουμε την κλάση "Pektis" με κάποιες επιπλέον μεθόδους.

// Η μέθοδος "rowSet" δέχεται μια εγγραφή παίκτη από την database και θέτει
// τα αντίστοιχα properties. Θέτει επίσης τις βάσεις για επιπλέον στοιχεία του
// παίκτη, π.χ. παράμετροι παίκτη κλπ. Η μέθοδος  χρησιμοποποιείται για
// αρχικοποίηση του παίκτη με βάση τα στοιχεία της database και επιστρέφει
// τον ίδιο τον παίκτη.

Pektis.prototype.rowSet = function(row) {
	this.login = globals.fetchProp(row, 'login');
	this.onoma = globals.fetchProp(row, 'onoma');
	return this;
}

// Η μέθοδος "peparamRowSet" δέχεται μια παράμετρο παίκτη από την database και την
// εντάσσει στη λίστα παραμέτρων του παίκτη.

Pektis.prototype.peparamRowSet = function(row) {
	this.peparamSet(row['param'], row['timi']);
	return this;
}

// Η μέθοδος "profinfoRowSet" δέχεται μια εγγραφή προφίλ παίκτη από την database
// και την εντάσσει στη λίστα προφίλ του παίκτη.

Pektis.prototype.profinfoRowSet = function(row) {
	var kimeno = row['kimeno'].rtrim();
	if (kimeno) this.profinfo[row['sxoliastis']] = row['kimeno'];
	else delete this.profinfo[row['sxoliastis']];
	return this;
}

// Η μέθοδος "peparamSetRow" δέχεται το είδος μιας παραμέτρου παίκτη και την τιμή
// τής παραμέτρου, και επιχειρεί να ενημερώσει την αντίστοιχη παράμετρο τού παίκτη
// στην database. Αν έχει δοθεί callback function καλείται αμέσως μετά την ενημέρωση
// με παραμέτρους τον παίκτη, το είδος τής παραμέτρου και την τιμή τής παραμέτρου.
// Η μέθοδος επιστρέφει τον παίκτη.

Pektis.prototype.peparamSetRow = function(param, timi, callback) {
	var pektis = this;
	var conn = DB.connection();
	var query = 'REPLACE `peparam` (`pektis`, `param`, `timi`) VALUES (' +
		conn.escape(pektis.login) + ',' + conn.escape(param) + ',' + conn.escape(timi) + ')';
	conn.query(query, function(conn) {
		conn.free();
		if (callback) callback(pektis, param, timi);
	});
	return this;
}

// Η μέθοδος "peparamAdiafori" δέχεται ως παραμέτρους το login name κάποιου
// παίκτη και το είδος παραμέτρου παίκτη και επιστρέφει true αν η παράμετρος
// ΔΕΝ αφορά τον συγκεκριμένο παίκτη, αλλιώς επιστρέφει true.

Pektis.prototype.peparamAdiafori = function(pektis, param) {
	if (pektis == this.login) return false;
	return Prefadoros.peparamIdiotiki(param);
}

// Η μέθοδος "feredataFormat" επιστρέφει τα δεδομένα του παίκτη σε μορφή κατάλληλη
// για αποστολή σε αίτημα παραλαβής δεδομένων. Ο παίκτης επιστρέφεται σε JSON format
// με τα πεδία του παίκτη και των παραμέτρων τού παίκτη σε συντομευμένη μορφή.
// Η παράμετρος "pios" είναι το login name του αιτούντος.

Pektis.prototype.feredataFormat = function(pios) {
	var pektis = {};
	pektis[Prefadoros.pektisEco('onoma')] = this.onoma;

	var peparam = {};
	var peparamYes = false;
	var that = this;
	globals.walk(this.peparam, function(param, timi) {
		if (that.peparamAdiafori(pios, param)) return;
		peparam[Prefadoros.peparamEco(param)] = timi;
		peparamYes = true;
	});
	if (peparamYes) pektis[Prefadoros.pektisEco('peparam')] = peparam;

	return globals.json(this.login) + ':' + globals.stringify(pektis);
}

// Η μέθοδος "kripseFila" δέχεται ένα string χαρτωσιάς 30 φύλλων και τη θέση τού
// παίκτη και επιστρέφει string χαρτωσιάς με κλειστά τα φύλλα των άλλων δύο παικτών.

Pektis.prototype.kripseFila = function(fila, thesi) {
	var plati, klista, anikta, i;

	plati = this.plati();
	klista = '';
	for (i = 0; i < 10; i++) klista += plati + 'V';

	anikta = fila.substr((thesi - 1) * 20, 20);
	switch (thesi) {
	case 1: return anikta + klista + klista;
	case 2: return klista + anikta + klista;
	case 3: return klista + klista + anikta;
	}

	globals.fatal('Pektis.kripseFila: ' + thesi + ': ακαθόριστη θέση παίκτη');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Prosklisi.prototype.setKodikos = function(kodikos) {
	if (kodikos != parseInt(kodikos)) globals.fatal('Prosklisi.setKodikos: ' + kodikos +
		': λανθασμένος κωδικός πρόσκλησης');

	this.kodikos = parseInt(kodikos);
	return this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Sxesi.prototype.rowSet = function(row) {
	this.pektis = row['pektis'];
	this.sxetizomenos = row['sxetizomenos'];
	switch (row['sxesi']) {
	case 'ΦΙΛΟΣ': this.sxesi = 'F'; break;
	case 'ΑΠΟΚΛΕΙΣΜΕΝΟΣ': this.sxesi = 'B'; break;
	default: globals.fatal('Sxesi.rowSet: ' + row['sxesi'] + ': ανύπαρκτη σχέση');
	}
	this.dimiourgia = row['dimiourgia'];
	return this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Στον server διατηρούμε λίστα παικτών που γράφουν στο πεδίο συζήτησης.
// Η λίστα δεικτοδοτείται με το login name του γράφοντος παίκτη και έχει
// τιμή το τραπέζι στο οποίο εκκίνησε η συγγραφή, ή null εφόσον η συγγραφή
// εκκίνησε στο καφενείο.

Skiniko.molivi = {};

// Η function "isMolivi" δέχεται το login name κάποιου παίκτη και επιστρέφει
// true εφόσον ο server έχει την αίσθηση ότι ο συγκεκριμένος παίκτης φαίνεται
// να γράφει κάποιο σχόλιο, αλλιώς επιστρέφει false.

Skiniko.isMolivi = function(login) {
	return Skiniko.molivi.hasOwnProperty(login);
}

Skiniko.oxiMolivi = function(login) {
	return !Skiniko.isMolivi(login);
}

// Η function "moliviAdd" δέχεται το login name κάποιου παίκτη και τον εντάσσει
// στους γράφοντες, ενημερώνοντας παράλληλα και τη σκηνική εικόνα της συζήτησης
// του τραπεζιού στο οποίο εκκινεί η συγγραφή, ή τη σκηνική εικόνα της συζήτησης
// του καφενείου, ανάλογα με το αν περάστηκε τραπέζι ή όχι.

Skiniko.moliviAdd = function(login, trapezi) {
	var sxolio, kinisi;

	if (Skiniko.isMolivi(login)) return;
	Skiniko.molivi[login] = trapezi;

	sxolio = new Sizitisi({'pektis': login});
	kinisi = new Kinisi({
		'?': 'MV',
		'p': login,
	});

	// Αν η γραφή σχολίου εκκίνησε σε τραπέζι, τότε θα προστεθεί το ανάλογο σήμα
	// στη συζήτηση του συγκεκριμένου τραπεζιού και στην κίνηση θα προστεθεί και
	// ο κωδικός αυτού του τραπεζιού.

	if (trapezi) {
		sxolio.sizitisiSet({
			'kimeno': Prefadoros.sxolioKodikos.moliviTrapezi
		}).sizitisiAdd(trapezi);
		kinisi.kinisiSet({'t': trapezi.kodikos});
	}

	// Αλλιώς ή γραφή εκκίνησε στο καφενείο, επομένως προσθέτουμε το ανάλογο σήμα
	// στη συζήτηση του καφενείου και η κίνηση θα σταλεί χωρίς κωδικό τραπεζιού.

	else sxolio.sizitisiSet({'kimeno': Prefadoros.sxolioKodikos.moliviKafenio}).sizitisiAdd();

	// Αποστέλλουμε την κίνηση όπως και αν αυτή έχει διαμορφωθεί.

	kinisi.kinisiAdd();
}

Skiniko.moliviDel = function(login) {
	var trapezi, sizitisi, i;

	if (Skiniko.oxiMolivi(login)) return;

	trapezi = Skiniko.molivi[login];
	delete Skiniko.molivi[login];

	sizitisi = trapezi ? trapezi.sizitisi : Skiniko.sizitisi;
	for (i = 0; i < sizitisi.length; i++) {
		if (sizitisi[i].pektis != login) continue;
		if (sizitisi[i].kimeno == Prefadoros.sxolioKodikos.moliviKafenio) sizitisi.splice(i, 1);
		else if (sizitisi[i].kimeno == Prefadoros.sxolioKodikos.moliviTrapezi) sizitisi.splice(i, 1);
	}

	(new Kinisi({
		'?': 'VM',
		'p': login,
	})).kinisiAdd();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Transaction log
// ---------------
// Το stack των κινήσεων στο οποίο σωρεύονται όλες οι κινήσεις που γίνονται.
// Οι σχετικές δομές και μέθοδοι εμπεριέχονται στο property "kinisi". Μπορεί
// κανείς να προβάλει ενστάσεις για το αν θα πρέπει οι κινήσεις να αποτελούν
// μέρος του σκηνικού, αλλά εγώ υποστηρίζω αυτή τη θέση. Από εδώ και στο
// εξής το stack των κινήσεων θα ονομάζεται "transaction log". Φανταστείτε
// το σκηνικό του server ως φυσικό μοντέλο· εκεί θα πρέπει να έχουμε μονάδες
// τραπεζιών και συνεδριών και -εγώ προτείνω- μια στοίβα όπου αποτίθενται οι
// νέες κινήσεις, το transaction log.

// Χρησιμοποιούμε array κινήσεων και όχι λίστα καθώς πρέπει οι κινήσεις να
// ακολουθούν τη σειρά με την οποία καταχωρήθηκαν στο transaction log.

Skiniko.kinisi = [];

// Η μέθοδος "kinisiWalk" διατρέχει όλες τις κινήσεις του transaction log
// με τη σειρά, από την παλαιότερη προς τη νεότερη.
// Για κάθε κίνηση καλεί την callback function που περνάμε ως παράμετρο με
// παραμέτρους το index της ανά χείρας κίνησης και την ίδια την κίνηση.
// Η μέθοδος επιστρέφει το πλήθος των κινήσεων του transaction log.

Skiniko.kinisiWalk = function(callback) {
	for (var i = 0; i < Skiniko.kinisi.length; i++) {
		if (callback) callback(i, Skiniko.kinisi[i]);
	}

	return i;
}

// Κάθε κίνηση πρέπει να περιέχει property "?" που δείχνει το είδος της
// κίνησης και άλλα properties που είναι ανάλογα με το είδος. Η μέθοδος
// δεν ελέγχει την ορθότητα ή την ακεραιότητα των παραπάνω στοιχείων.
// Η μέθοδος "idos" επιστρέφει το είδος της κίνησης.

Kinisi.prototype.idos = function() {
	if (!this.hasOwnProperty('?')) globals.fatal('Kinisi.idos: ακαθόριστο είδος κίνησης');
	return this['?'];
}

// Η μέθοδος "add" εισάγει νέα κίνηση στο array κινήσεων. Αμέσως μετά απαντάμε
// σε όλα τα ανοικτά αιτήματα "feredata" με σκοπό να επιστραφούν στους clients
// οι νέες κινήσεις.
//
// ΠΡΟΣΟΧΗ!!!
// Σ' αυτό το σημείο πιθανόν να παρουσιαστεί το εξής πρόβλημα: Καθώς η διαδικασία
// ενημέρωσης των "feredata" clients βρίσκεται σε (ασύγχρονη) εξέλιξη, μπορεί να
// προκύψει νέα κίνηση. Αυτό ελέγχεται μέσα στη μέθοδο "enimerosi" κρατώντας το
// μέγεθος του transaction log προ της αποστολής στον αντίστοιχο client και μετά
// το τέλος της αποστολής.

Kinisi.prototype.kinisiAdd = function(enimerosi) {
	var temp;

	// Κρατάμε στο property ":" stringified εκδοχή τής κινήσεως.

	this[':'] = globals.stringify(this);

	// Αν το είδος τής κίνησης επιδέχεται προσαρμογή, δημιουργώ ακριβές
	// αντίγραφο από τη stringified εκδοχή τής κίνησης και το αναθέτω
	// στην property "=". Αυτό το αντίγραφο θα υποστεί αργότερα τη
	// διαδικασία προσαρμογής.

	if (Kinisi.prosarmogiMap.hasOwnProperty(this.idos())) {
		try {
			eval('temp = ' + this[':'] + ';');
		} catch(e) {
			globals.fatal('Kinisi.add: "' + this[':'] + '": invertion failed');
		}

		this['='] = temp;
	}

	// Πριν εντάξουμε την κίνηση στο transaction log, ελέγχουμε μήπως
	// το transaction log χρειάζεται «κόντεμα».

	Kinisi.mazema();

	// Εντάσσουμε τώρα την ανά χείρας κίνηση στο transaction log.

	Skiniko.kinisi.push(this);
	if (enimerosi === undefined) enimerosi = true;
	if (!enimerosi) return this;

	// Ενημερώνουμε τους clients που έχουν ανοικτά αιτήματα αποστολής
	// δεδομένων.

	Skiniko.sinedriaEnimerosi();
	return this;
}

// Η μέθοδος "isAdiafori" δέχεται το όνομα ενός παίκτη και αν η κίνηση
// ΔΕΝ τον αφορά επιστρέφει true, αλλιώς επιστρέφει false.

Kinisi.prototype.isAdiafori = function(pektis, idos) {
	// Εαν δεν έχει καθοριστεί μέθοδος ελέγχου μη σχετικότητας της
	// κινήσεως, η κίνηση θεωρείται σημαντική.

	if (!Kinisi.adiaforiMap.hasOwnProperty(idos)) return false;

	// Επιστρέφουμε το αποτέλεσμα του ελέγχου ΜΗ σχετικότητας του
	// εν λόγω παίκτη με την ανά χείρας κίνηση.

	return Kinisi.adiaforiMap[idos].call(this, pektis);
}

// Η μέθοδος "prosarmogi" δέχεται το όνομα ενός παίκτη και ελέγχει για δεδομένα τής
// κίνησης που δεν αφορούν στον συγκεκριμένο παίκτη. Αν βρεθούν τέτοια δεδομένα, τότε
// επιστρέφεται προσαρμοσμένη stringified version τής κίνησης που έχει τοποθετηθεί στο
// property "~" από τη μέθοδο προσαρμογής, αλλιώς επιστρέφεται η ήδη έτοιμη stringified
// version της αρχικής κίνησης που βρίσκεται στο property ":".

Kinisi.prototype.prosarmogi = function(pektis, idos) {
	if (!Kinisi.prosarmogiMap.hasOwnProperty(idos)) return this[':'];
	if (Kinisi.prosarmogiMap[idos].call(this, pektis)) return this['~'];
	return this[':'];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Προσθέτουμε τώρα κάποια properties και κάποιες μεθόδους στην ίδια την
// κλάση και όχι στο prototype. Χρησιμοποιώ το object "Kinisi" ως name
// space για να κρύψω τα ανωτέρω αντικείμενα.

// Υπάρχουν κάποια είδη κινήσεων που ενδιαφέρουν μόνο επιλεγμένους παίκτες, π.χ.
// μια νέα πρόσκληση ενδιαφέρει μόνο τον οικοδεσπότη και τον προσκεκλημένο, μια
// δήλωση αγοράς αφορά μόνο τους παίκτες και τους θεατές ενός τραπεζιού κλπ. Τα
// είδη των κινήσεων αυτών περιγράφονται παρακάτω και μάλιστα συνοδεύονται από
// functions που ελέγχουν αν οι συγκεκριμένες κινήσεις αφορούν συγκεκριμένους
// παίκτες. Αυτές οι functions καλούνται ως μέθοδοι των κινήσεων και δέχονται ως
// παράμετρο το όνομα του παίκτη προς τον οποίο πρόκειται να γίνει η αποστολή, ενώ
// επιστρέφουν true εφόσον δεν τον αφορά η συγκριμένη κίνηση και false εφόσον η
// κίνηση τον αφορά.

Kinisi.adiaforiMap = {
	// Πρόσκληση. Αφορά τον οικοδεσπότη και τον προσκεκλημένο.

	'NL': function(pektis) {
		if (this.a == pektis) return false;
		if (this.p == pektis) return false;
		return true;
	},

	// Διαγραφή πρόσκλησης. Αφορά τον οικοδεσπότη και τον προσκεκλημένο.

	'LN': function(pektis) {
		if (this.a == pektis) return false;
		if (this.p == pektis) return false;
		return true;
	},

	// Αλλαγή παραμέτρου παίκτη. Για τις ιδιωτικές παραμέτρους αφορά μόνο
	// τον συγκεκριμένο παίκτη.

	'PPRM': function(pektis) {
		if (this.p == pektis) return false;
		return Prefadoros.peparamIdiotiki(Prefadoros.peparamOce(this.r));
	},

	// Καθορισμός σχέσης. Αφορά μόνο τον παίκτη που καθορίζει τη σχέση.

	'SX': function(pektis) {
		return(this.p != pektis);
	},

	// Ένδειξη γραφής σχολίου συζήτησης. Δεν αφορά τον γράφοντα. Για τους
	// υπόλοιπους αφορά σε όλους εφόσον πρόκειται για τη δημόσια συζήτηση,
	// αλλιώς αφορά μόνο τους παίκτες/θεατές του τραπεζιού.

	'MV': function(pektis) {
		var sinedria;

		// Αν ο παίκτης είναι ο γράφων, δεν χρειάζεται να παραλάβει
		// την κίνηση καθώς ο ίδιος δεν χρειάζεται ένδειξη.

		if (this.p == pektis) return true;

		// Αν η γραφή γίνεται σε mode καφενείου δεν έχει καθοριστεί
		// τραπέζι και τότε η ένδειξη αφορά τους πάντες.

		if (!this.hasOwnProperty('t')) return false;

		// Η γραφή γίνεται σε mode τραπεζιού, οπότε η ένδειξη
		// αφορά τους εμπλεκόμενους με το τραπέζι.

		sinedria = Skiniko.isSinedria(pektis);
		if (sinedria.oxiTrapezi()) return true;

		return(sinedria.trapezi != this.fetchProp('t'));
	},

	// Δημοσιοποίηση σχολίου συζήτησης. Δεν αφορά τον γράφοντα. Για τους
	// υπόλοιπους αφορά σε όλους εφόσον πρόκειται για τη δημόσια συζήτηση,
	// αλλιώς αφορά μόνο τους παίκτες/θεατές του τραπεζιού.

	'SZ': function(pektis) {
		var sinedria;

		// Αν ο παίκτης είναι ο γράφων, δεν χρειάζεται να παραλάβει
		// την κίνηση καθώς προσθέσαμε το σχόλιο στη συζήτηση κατά
		// την αποστολή.

		if (this.p == pektis) return true;

		// Αν η γραφή γίνεται σε mode καφενείου δεν έχει καθοριστεί
		// τραπέζι και τότε η ένδειξη αφορά τους πάντες.

		if (!this.hasOwnProperty('t')) return false;

		// Η γραφή γίνεται σε mode τραπεζιού, οπότε η ένδειξη
		// αφορά τους εμπλεκόμενους με το τραπέζι.

		sinedria = Skiniko.isSinedria(pektis);
		if (sinedria.oxiTrapezi()) return true;

		return(sinedria.trapezi != this.fetchProp('t'));
	},

	// Διαγραφή σχολίου/σχολίων συζήτησης. Αφορά μόνο τους παίκτες/θεατές του
	// τραπεζιού. Για τη δημόσια συζήτηση δεν τίθεται θέμα καθώς δεν επιτρέπεται
	// διαγραφή.

	'DZ': function(pektis) {
		var sinedria = Skiniko.isSinedria(pektis);
		if (!sinedria) return true;
		if (sinedria.oxiTrapezi()) return true;
		return(sinedria.trapezi != this.fetchProp('t'));
	},

	// Μπορεί να ακούγεται ενδιαφέρον το να γνωρίζουμε ποιον παίκτη παρακολουθεί
	// κάποιος θεατής, αλλά πιστεύω ότι το θέμα άπτεται της αρχής προστασίας
	// προσωπικών δεδομένων, επομένως ακυρώνουμε την πληροφορία.

	'HH': function(pektis) {
		return(this.p != pektis);
	},

	// Ενέργεια στα πλαίσια της τρέχουσας διανομής τραπεζιού. Οι ενέργειες αφορούν
	// μόνο τους παίκτες του τραπεζιού.

	'EG': function(pektis) {
		var sinedria = Skiniko.isSinedria(pektis);
		if (!sinedria) return true;
		if (sinedria.oxiTrapezi()) return true;
		return(sinedria.trapezi != this.fetchProp('t'));
	},
};

// Ακολουθούν μέθοδοι ελέγχου και προσαρμογής δεδομένων για διάφορα είδη κινήσεων.
// Οι μέθοδοι αυτές επιστρέφουν true αν κρίνουν αναγκαία την προσαρμογή, αλλιώς
// επιστρέφουν false.
//
// Στην περίπτωση κατά την οποία η προσαρμογή κριθεί αναγκαία, ελέγχουν την ύπαρξη
// ήδη προσαρμοσμένης stringified εκδοχής τής κινήσεως στο property "~". Αν υπάρχει
// ήδη προσαρμοσμένη stringified εκδοχή τής κινήσεως, επιστρέφουν πάραυτα με τιμή
// true, αλλιώς προχωρούν στην προσαρμογή τής κινήσεως προσαρμόζοντας κατάλληλα το
// αντίγραφο τής αρχικής κίνησης που υπάρχει εκ κατασκευής στο property "=". Κατόπιν
// αναθέτουν την stringified εκδοχή τού προσαρμοσμένου αντιγράφου στο property "~",
// διαγράφουν το αντίγραφο, και επιστρέφουν true.

Kinisi.prosarmogiMap = {
	// Στην  πρώτη μέθοδο προσαρμογής επεξηγούμε αναλυτικά όλα τα βήματα τής
	// προσαρμογής, ώστε να χρησιμοποηθεί ως οδηγός και στις επόμενες μεθόδους
	// προσαρμογής.

	'SN': function(pektis) {
		// Έλεγχος αναγκαιότητας προσαρμογής για τον εν λόγω παίκτη.
		// Αν δεν απαιτείται προσαρμογή επιστρέφεται false.

		if (pektis == this.p) return false;

		// Απαιτείται προσαρμογή. Αν έχει γίνει ήδη προσαρμογή από προηγούμενη
		// κλήση (για άλλον παίκτη), επιστρέφεται true και θα χρησιμοποιηθεί
		// η ήδη έτοιμη stringified εκδοχή τής προσαρμοσμένης κινήσεως που
		// πρέπει να έχει ανατεθεί στο property "~".

		if (this.hasOwnProperty('~')) return true;

		// Είναι η πρώτη φορά που η συγκεκριμένη κίνηση υφίσταται τη διαδικασία
		// προσαρμογής, επομένως παίρνουμε το αντίγραφο τής αρχικής κίνησης και
		// πάνω σ' αυτό εφαρμόζουμε τη διαδικασία προσαρμογής.

		var kinisi = this['='];
		var prosarmogi = false;
		for (var i in kinisi.r) {
			if (Prefadoros.peparamDimosia(Prefadoros.peparamOce(i))) continue;
			delete kinisi.r[i];
			prosarmogi = true;
		}

		// Κρατάμε την strigified εκδοχή της προσαρμοσμένης κίνησης στο property "~".
		// Εαν, όμως, δεν έγιναν πραγματικές αλλαγές κατά τη διαδικασία προσαρμογής,
		// αποφεύγουμε τη διαδικασία του stringify, αλλά χρησιμοποιούμε την ήδη έτοιμη
		// εκδοχή τής αρχικής κινήσεως.

		this['~'] = prosarmogi ? globals.stringify(kinisi) : this[':'];

		// Το αντίγραφο δεν χρειάζεται πλέον, επομένως το διαγράφουμε.

		delete this['='];

		// Επιστρέφουμε true, πράγμα που σημαίνει ότι κρίθηκε αναγκαία και
		// εφαρμόστηκε προσαρμογή τής κινήσεως.

		return true;
	},

	'PR': function(pektis) {
		if (pektis == this.p) return false;
		if (this.hasOwnProperty('~')) return true;

		var kinisi = this['='];
		var prosarmogi = false;
		for (var i in kinisi.r) {
			if (Prefadoros.peparamDimosia(Prefadoros.peparamOce(i))) continue;
			delete kinisi.r[i];
			prosarmogi = true;
		}

		this['~'] = prosarmogi ? globals.stringify(kinisi) : this[':'];
		delete this['='];
		return true;
	},

	'LN': function(pektis) {
		if (this.hasOwnProperty('~')) return true;

		var kinisi = this['='];
		delete kinisi.a;
		delete kinisi.p;

		this['~'] = globals.stringify(kinisi);
		delete this['='];
		return true;
	},

	// Ακολουθεί μέθοδος προσαρμογής ενεργειών. Εδώ τα πράγματα είναι λίγο περίπλοκα
	// καθώς η προσαρμογή που αφορά σε κάποιον παίκτη μπορεί να είναι διαφορετική  από
	// αυτήν που αφορά σε άλλον παίκτη, επομένως η προσαρμοσμένη κίνηση δεν βολεύει και
	// θα πρέπει να υπολογίζεται εκ νέου.

	'EG': function(pektis) {
		var sinedria, idos;

		// Θα γίνει προσαρμογή μόνο στα δεδομένα που απευθύνονται σε παίκτες τού
		// τραπεζιού. Θεατές και άλλοι παραλήπτες θα παραλάβουν την κίνηση χωρίς
		// προσαρμογή.

		sinedria = Skiniko.isSinedria(pektis);
		if (!sinedria) return false;
		if (sinedria.oxiTrapezi()) return false;
		if (sinedria.trapezi != this.fetchProp('t')) return false;
		if (sinedria.oxiPektis()) return false;

		// Στο σημείο αυτό έχουμε διασφαλίσει ότι πρόκειται γαι παίκτη τού τραπεζιού
		// στο οποίο αφορά η ανά χείρας ενέργεια.

		idos = Prefadoros.energiaIdosOce(this.fetchProp('i'));

		// Τα περισσότερα είδη ενεργειών δεν επιδέχονται προσαρμογή, επομένως
		// αποστέλλονται αναλλοίωτα τα δεδομένα της κίνησης.

		if (!Kinisi.prosarmogiEnergiaMap.hasOwnProperty(idos)) return false;

		// Εαν τα δεδομένα χρήζουν προσαρμογής, τότε θα αποσταλλούν τα δεδομένα
		// που υπέστησαν την προσαρμογή.

		if (Kinisi.prosarmogiEnergiaMap[idos].call(this, sinedria)) return true;

		// Αλλιώς αποστέλλονται αναλλοίωτα τα δεδομένα της κίνησης.

		return false;
	},
};

// Οι μέθοδοι προσαρμογής ενεργειών αφορούν μόνο σε παίκτες και όχι σε θεατές.
// Πράγματι, οι προσαρμογή των ενεργειών αφορά σε ευαίσθητα δεδομένα τα οποία
// δεν πρέπει να κατευθυνθούν στους αντιπάλους.

Kinisi.prosarmogiEnergiaMap = {
	// Μετά από ενέργεια τύπου "ΔΗΛΩΣΗ" υπάρχει περίπτωση το τραπέζι να έχει
	// περιέλθει σε φάση "ΑΛΛΑΓΗ". Πράγματι, η τελευταία δήλωση που καθορίζει
	// τον τζογαδόρο θέτει το τραπέζι σε φάση "ΑΛΛΑΓΗ". Σ' αυτή την περίπτωση,
	// στα data της ενέργειας εμπεριέχονται και τα φύλλα του τζόγου. Αυτό είναι
	// καλό για τον τζογαδόρο και όσους τον παρακολουθούν, αλλά δεν είναι πρέπον
	// για τους αντιπάλους. Προσαρμόζουμε, λοιπόν, τα δεδομένα που απευθύνονται
	// στους αντιπάλους, διαγράφοντας τα φύλλα τού τζόγου.

	'ΔΗΛΩΣΗ': function(sinedria) {
		var kinisi, data, trapezi = Skiniko.fetchTrapezi(sinedria.trapezi);

		switch (trapezi.fasi) {
		case 'ΑΛΛΑΓΗ':
			// Ο τζοαγαδόρος παραλαμβάνει αναλλοίωτη την κίνηση της
			// ενέργειας με τα φύλλα τού τζόγου να περιέχονται στα
			// δεδομένα.

			if (sinedria.thesi == trapezi.tzogadoros) return false;

			// Αν έχει γίνει ήδη προσαρμογή δεν κάνουμε καμία επιπλέον
			// ενέργεια.

			if (this.hasOwnProperty('~')) return true;

			// Αφαιρούμε από τα δεδομένα τής κίνησης τα φύλλα τού τζόγου.

			kinisi = this['='];
			data = kinisi.d.split(':');
			kinisi.d = data[0];
			this['~'] = globals.stringify(kinisi);
			kinisi.d = this.d;
			return true;
		}

		return false;
	},

	// Στα δεδομένα της ενέργειας τύπου "ΔΙΑΝΟΜΗ" εμπεριέχονται τα φύλλα των
	// τριών παικτών. Αυτό δεν είναι πρέπον όσον αφορά στους αντιπάλους. Κάθε
	// παίκτης τού τραπεζιού θα παραλάβει μόνο τα δικά του φύλλα ανοικτά.

	'ΔΙΑΝΟΜΗ': function(sinedria) {
		var kinisi;

		kinisi = this['='];
		kinisi.d = sinedria.kripseFila(kinisi.d);
		this['~'] = globals.stringify(kinisi);
		kinisi.d = this.d;
		return true;
	}
};

// Το transaction log δεν πρέπει να αφήνεται να μεγαλώνει ανεξέλεγκτα.
// Πριν από κάθε εισαγωγή νέας κίνησης ελέγχεται το μέγεθος του stack
// και αν ξεπερνά κάποιο όριο γίνεται απόπειρα συμμαζέματος.

Kinisi.maxNormal = 1000;
Kinisi.maxAfxisi = 500;
Kinisi.max = Kinisi.maxNormal;

Kinisi.mazema = function() {
	if (Skiniko.kinisi.length < Kinisi.max) return;

	// Έχουμε αγγίξει το κρίσιμο μέγεθος και θα γίνει απόπειρα συμμαζέματος.
	// Θα πρέπει να διατρέξουμε όλες τις συνεδρίες και να εντοπίσουμε τον
	// μικρότερο δείκτη στο transaction log. Το κομμάτι πριν από αυτόν τον
	// ελάχιστο δείκτη μπορεί να αποκοπεί και να μειωθούν ανάλογα όλοι οι
	// επιμέρους δείκτες στα ανοικτά αιτήματα παραλαβής δεδομένων.

	var min = Skiniko.kinisi.length;
	Skiniko.sinedriaWalk(function(pektis, sinedria) {
		if (sinedria.kinisi < min) min = sinedria.kinisi;
	});

	// Αν κάποιος client δεν έχει ενημερωθεί με μέρος έστω των κινήσεων
	// του transaction log, τότε έχουμε πρόβλημα και το transaction log
	// δεν μπορεί να «κοντύνει». Σ' αυτή την περίπτωση αυξάνουμε το όριο
	// μήπως και τα καταφέρουμε την επόμενη φορά.

	if (min <= 0) {
		Kinisi.max += Kinisi.maxAfxisi;
		console.error('transaction log limit raised to ' + Kinisi.max);
		return;
	}

	// Προβαίνουμε τώρα στη μέιωση του μεγέθους του transaction log
	// αποκόπτοντας το τμήμα που έχει αποσταλεί σε όλους τους clients.

	console.log('shortening transaction log from ' + Skiniko.kinisi.length + ' to ' + (Skiniko.kinisi.length - min));
	Skiniko.kinisi.splice(0, min);

	// Μειώνουμε ανάλογα τους δείκτες κίνησης στα ανοικτά αιτήματα
	// παραλαβής δεδομένων.

	Skiniko.sinedriaWalk(function(pektis, sinedria) {
		sinedria.kinisi -= min;
	});

	// Αν οι συνθήκες το επιτρέπουν επαναφέρω το όριο στα αρχικά του επίπεδα.

	if (Kinisi.max <= Kinisi.maxNormal) return;
	if (Skiniko.kinisi.length > Kinisi.maxNormal) return;
	Kinisi.max = Kinisi.maxNormal;
	console.error('transaction log limit reset to ' + Kinisi.max);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Το σκηνικό στήνεται από τα στοιχεία που βρίσκονται κρατημένα στην database,
// και πριν ξεκινήσει ο server να παρέχει τις υπηρεσίες του. Όπως θα δείτε, οι
// διάφορες διαδικασίες καλούνται αλυσιδωτά προκειμένου να διασφαλιστεί σειριακή
// ακολουθία.

Skiniko.stisimo = {
	enarxi: function() {
		Log.fasi.nea('reconstructing scenery');
		Skiniko.stisimo.plist = {};
		var conn = DB.connection();
		Skiniko.stisimo.prosklisiCleanup(conn);
	},

	prosklisiCleanup: function(conn) {
		Log.print('deleting orphan "prosklisi" rows from the database');
		query = 'DELETE FROM `prosklisi` WHERE `trapezi` NOT IN ' +
			'(SELECT `kodikos` FROM `trapezi` WHERE `arxio` IS NULL)';
		conn.query(query, function(conn) {
			Skiniko.stisimo.trapezi(conn);
		});
	},

	trapezi: function(conn) {
		Log.print('reading "trapezi" rows from the database');
		query = 'SELECT `kodikos`, UNIX_TIMESTAMP(`stisimo`) AS `stisimo`, `pektis1`, ' +
			'`apodoxi1`, `pektis2`, `apodoxi2`, `pektis3`, `apodoxi3` ' +
			'FROM `trapezi` WHERE `arxio` IS NULL';
		conn.query(query, function(conn, rows) {
			for (var i = 0; i < rows.length; i++) {
				(new Trapezi()).rowSet(rows[i]).trapeziAdd();
				if (rows[i].pektis1) Skiniko.stisimo.plist[rows[i].pektis1] = 1;
				if (rows[i].pektis2) Skiniko.stisimo.plist[rows[i].pektis2] = 1;
				if (rows[i].pektis3) Skiniko.stisimo.plist[rows[i].pektis3] = 1;
			}

			Skiniko.stisimo.trparam(conn);
		});
	},

	trparam: function(conn) {
		Log.print('reading "trparam" rows from the database');
		query = 'SELECT `trapezi`, `param`, `timi` FROM `trparam` ' +
			'WHERE `trapezi` IN (SELECT `kodikos` FROM `trapezi` ' +
			'WHERE `arxio` IS NULL)';
		conn.query(query, function(conn, rows) {
			var trapezi;
			for (i = 0; i < rows.length; i++) {
				trapezi = Skiniko.isTrapezi(rows[i].trapezi);
				if (trapezi) trapezi.trparamSet(rows[i].param, rows[i].timi);
			}

			Skiniko.stisimo.prosklisi(conn);
		});
	},

	prosklisi: function(conn) {
		Log.print('reading "prosklisi" rows from the database');
		query = 'SELECT `kodikos`, `trapezi`, `apo`, `pros`, UNIX_TIMESTAMP(`epidosi`) AS `epidosi` ' +
			'FROM `prosklisi` ORDER BY `kodikos`';
		conn.query(query, function(conn, rows) {
			for (var i = 0; i < rows.length; i++) {
				if (Skiniko.oxiTrapezi(rows[i].trapezi)) {
					console.error(rows[i].kodikos + ': ορφανή πρόσκληση');
					continue;
				}

				(new Prosklisi(rows[i])).prosklisiAdd();
				Skiniko.stisimo.plist[rows[i].apo] = 1;
				Skiniko.stisimo.plist[rows[i].pros] = 1;
			}

			Skiniko.stisimo.dianomi(conn);
		});
	},

	// Με την function "dianomi" διαβάζουμε εντάσσουμε στο σκηνικό τις διανομές των
	// ενεργών τραπεζιών. Φυσικά οι διανομές πρέπει να ενταχθούν διατεταγμένα.

	dianomi: function(conn) {
		var teldia = {};

		Log.print('reading "dianomi" rows from the database');
		query = 'SELECT `kodikos`, `trapezi`, UNIX_TIMESTAMP(`enarxi`) AS `epidosi`, ' +
			'`dealer`, `kasa1`, `metrita1`, `kasa2`, `metrita2`, `kasa3`, `metrita3`, ' +
			'UNIX_TIMESTAMP(`telos`) AS `telos` FROM `dianomi` ' +
			'WHERE `trapezi` IN (SELECT `kodikos` FROM `trapezi` WHERE `arxio` IS NULL) ' +
			'ORDER BY `kodikos`';
		conn.query(query, function(conn, rows) {
			var trapezi;
			for (i = 0; i < rows.length; i++) {
				trapezi = Skiniko.isTrapezi(rows[i].trapezi);
				if (!trapezi) continue;

				// Εντάσσουμε την ανά χείρας διανομή στο array διανομών τού τραπεζιού.

				trapezi.dianomiAdd((new Dianomi()).rowSet(rows[i]));

				// Παράλληλα κρατάμε στη λίστα "teldia" τον κωδικό τής τελευταίας διανομής
				// για κάθε τραπέζι. Αυτό θα μας χρειαστεί αργότερα για να διαβάσουμε τις
				// ενέργειες της τελευταίας διανομής κάθε τραπεζιού.

				teldia[trapezi.kodikos] = rows[i]['kodikos'];
			}

			Log.print('reading "energia" rows from the database');
			Skiniko.stisimo.energia(conn, teldia);
		});
	},

	// Με την function "energia" θα εντάξουμε στο σκηνικό τις ενέργειες της τελευταίας
	// διανομής κάθε ενεργού τραπεζιού. Αυτό θα γίνει με τη βοήθεια τής λίστας "teldia"
	// στην οποία έχουμε τον κωδικό της τελευταίας διανομής για κάθε ενεργό τραπέζι.

	energia: function(conn, teldia) {
		var i, trapezi;

		// Εκκινούμε μια διαδικασία τάχα για όλα τα τραπέζια της λίστας "teldia", αλλά
		// μετά το πρώτο τραπέζι που θα διαχειριστούμε, διαγράφουμε το τραπέζι και
		// επανεκκινούμε τη διαδικασία. Με αυτόν τον τρόπο εκτελούμε την ένταξη των
		// ενεργειών αλυσιδωτά, με σειριακό τρόπο.

		for (i in teldia) {
			trapezi = Skiniko.fetchTrapezi(i);
			query = 'SELECT `kodikos`, `dianomi`, `pektis`, `idos`, `data`, ' +
				'UNIX_TIMESTAMP(`pote`) AS `pote` FROM `energia` WHERE `dianomi` = ' +
				teldia[i] + ' ORDER BY `kodikos`';

			// Διαγράφουμε από τη λίστα τελευταίων διανομών των τραπεζιών το τρέχον
			// τραπέζι, ούτως ώστε την επόμενη φορά να διαχειριστούμε τα υπόλοιπα
			// τραπέζια τής λίστας.

			delete teldia[i];

			conn.query(query, function(conn, rows) {
				var i;

				for (i = 0; i < rows.length; i++) {
					trapezi.energiaAdd(new Energia(rows[i]));
				}

				// Επανεκκινούμε τη διαδικασία για τα υπόλοιπα τραπέζια
				// τής λίστας τελευταίων διανομών.

				Skiniko.stisimo.energia(conn, teldia);
			});

			return;
		}

		// Δεν έτρεξε το παραπάνω loop, επομένως τελείωσαν τα τραπέζια και
		// προχωρούμε στις συνεδρίες.

		Skiniko.stisimo.sinedria(conn);
	},

	sinedria: function(conn) {
		Log.print('reading "sinedria" rows from the database');
		query = 'SELECT `pektis`, `klidi`, `ip`, UNIX_TIMESTAMP(`isodos`) AS `isodos`, ' +
			'`trapezi`, `thesi`, `simetoxi` FROM `sinedria`';
		conn.query(query, function(conn, rows) {
			conn.free();
			for (var i = 0; i < rows.length; i++) {
				(new Sinedria()).rowSet(rows[i]).sinedriaAdd();
				Skiniko.stisimo.plist[rows[i].pektis] = 1;
			}

			Log.print('reading "pektis" rows from the database');
			Skiniko.stisimo.pektis();
		});
	},

	// Θα φορτώσουμε τώρα όλους τους εμπλεκόμενους παίκτες μαζί με τα στοιχεία
	// που τους αφορούν, π.χ. παραμέτρους, προφίλ, σχέσεις κλπ. Το φόρτωμα των
	// παικτών θα γίνει για τους παίκτες τής λίστας "plist" την οποία κτίσαμε
	// κατά το φόρτωμα των στοιχείων που έχουν προηγηθεί. Η function "pektis"
	// φορτώνει μόνο τον πρώτο παίκτη της λίστας η οποία, όμως, βαίνει μειούμενη.

	pektis: function() {
		for (var login in Skiniko.stisimo.plist) {
			delete Skiniko.stisimo.plist[login];
			if (login) Service.pektis.load(login, function() {
				Skiniko.stisimo.pektis()
			}, function(pektis) {
				globals.fatal('Skiniko.stisimo.pektis: ' + login + ': "pektis" row not found');
			});
			return;
		}

		// Η λίστα των παίκτών έχει εξανληθεί, επομένως προχωρούμε
		// στην επόμενη φάση.

		Skiniko.stisimo.ready();
	},

	// Όταν τελειώσουν οι διαδικασίες υποδομής και έχει στηθεί το σκηνικό τού
	// server, ενεργοποιούμε τις πόρτες υποδοχής αιτημάτων (normal και monitor)
	// και βάζουμε μπρος τις διαδικασίες περιπόλου. Η μέθοδος "ready" είναι το
	// τελευταίο βήμα κατά το στήσιμο του Node server.

	ready: function() {
		// Έχουν ενταχθεί στο σκηνικό του server όλα τα δεδομένα με βάση
		// τα οποία θα ελέγξουμε τα στοιχεία θέσης των συνεδριών.

		Skiniko.sinedriaWalk(function(login, sinedria) {
			// Αν δεν έχουμε στοιχεία θέσης εντοπίζουμε τώρα.
			if (sinedria.oxiTrapezi()) return sinedria.entopismos();

			// Υπάρχουν στοιχεία θέσης και ελέγχουμε την ύπαρξη του τραπεζιού.
			var trapezi = Skiniko.isTrapezi(sinedria.trapezi);
			if (!trapezi) return sinedria.entopismos();

			// Ελέγχουμε την εγκυρότητα της θέσης.
			if (Prefadoros.oxiThesi(sinedria.thesi)) return sinedria.entopismos();

			// Υπάρχει τραπέζι και η θέση είναι έγκυρη. Αν είναι θεατής επιστρέφουμε.
			if (sinedria.theatis) return;

			// Είναι παίκτης και ελέγχουμε την ορθότητα της θέσης.
			if (trapezi.pektis(sinedria.thesi) == sinedria.pektis) return;

			// Υπάρχει κάποιο πρόβλημα και προβαίνουμε σε επανεντοπισμό.
			sinedria.entopismos();
		});

		// Λαμβάνουμε μέριμνα για διάφορα events τερματισμού του Node server,
		// π.χ. φροντίζουμε κατά τον τερματισμό να ενημερώσουμε την database
		// κλπ.

		Server.atexit();

		// Βάζουμε μπροστά την κύρια λειτουργία του server, δηλαδή τη διαχείριση
		// αιτημάτων προς τη βασική πόρτα.

		Log.fasi.nea('establishing listeners');
		Server.oriste();

		// Βάζουμε μπροστά διάφορες λειτουργίες περιοδικών ελέγχων, π.χ. κλείσιμο
		// ανενεργών συνδέσεων με την database, αρχειοθέτηση ανενεργών παικτών κλπ.

		Peripolos.setup();

		// Ενεργοποιούμε την πόρτα ελέγχου. Στην πόρτα ελέγχου απευθύνουμε αιτήματα
		// που μας δίνουν εσωτερική εικόνα του σκηνικού στον server.

		Monitor.oriste();

		// Διαγράφουμε από τη μνήμη όλη τη διαδικασία στησίματος, καθώς δεν θα μας
		// ξαναχρειαστούν οι συγκεκριμένες μέθοδοι.

		setTimeout(function() {
			delete Skiniko.stisimo;
		}, 100);

		Log.fasi.nea('node server is up and running!');
	},
};
